## @ GenCfgOpt.py
#
# Copyright (c) 2014 - 2015, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials are licensed and made available under
# the terms and conditions of the BSD License that accompanies this distribution.
# The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php.
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
##

import os
import re
import sys
import struct
from   datetime import date

# Generated file copyright header

__copyright_txt__ = """## @file
#
#  THIS IS AUTO-GENERATED FILE BY BUILD TOOLS AND PLEASE DO NOT MAKE MODIFICATION.
#
#  This file lists all VPD informations for a platform collected by build.exe.
#
# Copyright (c) %4d, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution.  The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
"""

__copyright_bsf__ = """/** @file

  Boot Setting File for Platform Configuration.

  Copyright (c) %4d, Intel Corporation. All rights reserved.<BR>
  This program and the accompanying materials
  are licensed and made available under the terms and conditions of the BSD License
  which accompanies this distribution.  The full text of the license may be found at
  http://opensource.org/licenses/bsd-license.php

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

  This file is automatically generated. Please do NOT modify !!!

**/

"""

__copyright_h__ = """/** @file

Copyright (c) %4d, Intel Corporation. All rights reserved.<BR>

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
  list of conditions and the following disclaimer in the documentation and/or
  other materials provided with the distribution.
* Neither the name of Intel Corporation nor the names of its contributors may
  be used to endorse or promote products derived from this software without
  specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  THE POSSIBILITY OF SUCH DAMAGE.

  This file is automatically generated. Please do NOT modify !!!

**/
"""

def UpdateMemSiUpdInitOffsetValue (DscFile):
    DscFd        = open(DscFile, "r")
    DscLines     = DscFd.readlines()
    DscFd.close()

    DscContent = []
    MemUpdInitOffset = 0
    SiUpdInitOffset = 0
    MemUpdInitOffsetValue = 0
    SiUpdInitOffsetValue = 0

    while len(DscLines):
        DscLine  = DscLines.pop(0)
        DscContent.append(DscLine)
        DscLine = DscLine.strip()
        Match = re.match("^([_a-zA-Z0-9]+).(MemoryInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine)
        if Match:
            MemUpdInitOffsetValue = int(Match.group(5), 0)
        Match = re.match("^\s*([_a-zA-Z0-9]+).(SiliconInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine)
        if Match:
            SiUpdInitOffsetValue = int(Match.group(5), 0)
        Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(0x244450554D454D24)",DscLine)
        if Match:
            MemUpdInitOffset = int(Match.group(3), 0)
        Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(0x244450555F495324)",DscLine)
        if Match:
            SiUpdInitOffset = int(Match.group(3), 0)

    if MemUpdInitOffsetValue != MemUpdInitOffset or SiUpdInitOffsetValue != SiUpdInitOffset:
        MemUpdInitOffsetStr = "0x%08X" % MemUpdInitOffset
        SiUpdInitOffsetStr = "0x%08X" % SiUpdInitOffset
        DscFd = open(DscFile,"w")
        for DscLine in DscContent:
            Match = re.match("^\s*([_a-zA-Z0-9]+).(MemoryInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine)
            if Match:
                 DscLine = re.sub(r'(?:[^\s]+\s*$)', MemUpdInitOffsetStr + '\n', DscLine)
            Match = re.match("^\s*([_a-zA-Z0-9]+).(SiliconInitUpdOffset)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine)
            if Match:
                 DscLine = re.sub(r'(?:[^\s]+\s*$)', SiUpdInitOffsetStr + '\n', line)
            DscFd.writelines(DscLine)
        DscFd.close()

class CLogicalExpression:
    def __init__(self):
        self.index    = 0
        self.string   = ''

    def errExit(self, err = ''):
        print "ERROR: Express parsing for:"
        print "       %s" % self.string
        print "       %s^" % (' ' * self.index)
        if err:
            print "INFO : %s" % err
        raise SystemExit

    def getNonNumber (self, n1, n2):
        if not n1.isdigit():
            return n1
        if not n2.isdigit():
            return n2
        return None

    def getCurr(self, lens = 1):
        try:
            if lens == -1:
                return self.string[self.index :]
            else:
                if self.index + lens > len(self.string):
                    lens = len(self.string) - self.index
                return self.string[self.index : self.index + lens]
        except Exception:
            return ''

    def isLast(self):
        return self.index == len(self.string)

    def moveNext(self, len = 1):
        self.index += len

    def skipSpace(self):
        while not self.isLast():
            if self.getCurr() in ' \t':
                self.moveNext()
            else:
                return

    def normNumber (self, val):
        return True if val else False

    def getNumber(self, var):
        var = var.strip()
        if   re.match('^0x[a-fA-F0-9]+$', var):
            value = int(var, 16)
        elif re.match('^[+-]?\d+$', var):
            value = int(var, 10)
        else:
            value = None
        return value

    def parseValue(self):
        self.skipSpace()
        var = ''
        while not self.isLast():
            char = self.getCurr()
            if re.match('^[\w.]', char):
                var += char
                self.moveNext()
            else:
                break
        val = self.getNumber(var)
        if val is None:
            value = var
        else:
            value = "%d" % val
        return value

    def parseSingleOp(self):
        self.skipSpace()
        if re.match('^NOT\W', self.getCurr(-1)):
            self.moveNext(3)
            op  = self.parseBrace()
            val = self.getNumber (op)
            if val is None:
                self.errExit ("'%s' is not a number" % op)
            return "%d" % (not self.normNumber(int(op)))
        else:
            return self.parseValue()

    def parseBrace(self):
        self.skipSpace()
        char = self.getCurr()
        if char == '(':
            self.moveNext()
            value = self.parseExpr()
            self.skipSpace()
            if self.getCurr() != ')':
                self.errExit ("Expecting closing brace or operator")
            self.moveNext()
            return value
        else:
            value = self.parseSingleOp()
            return value

    def parseCompare(self):
        value = self.parseBrace()
        while True:
            self.skipSpace()
            char = self.getCurr()
            if char in ['<', '>']:
                self.moveNext()
                next = self.getCurr()
                if next == '=':
                    op = char + next
                    self.moveNext()
                else:
                    op = char
                result = self.parseBrace()
                test = self.getNonNumber(result, value)
                if test is None:
                    value = "%d" % self.normNumber(eval (value + op + result))
                else:
                    self.errExit ("'%s' is not a valid number for comparision" % test)
            elif char in ['=', '!']:
                op = self.getCurr(2)
                if op in ['==', '!=']:
                    self.moveNext(2)
                    result = self.parseBrace()
                    test = self.getNonNumber(result, value)
                    if test is None:
                        value = "%d" % self.normNumber((eval (value + op + result)))
                    else:
                        value = "%d" % self.normNumber(eval ("'" + value + "'" + op + "'" + result + "'"))
                else:
                    break
            else:
                break
        return value

    def parseAnd(self):
        value = self.parseCompare()
        while True:
            self.skipSpace()
            if re.match('^AND\W', self.getCurr(-1)):
                self.moveNext(3)
                result = self.parseCompare()
                test = self.getNonNumber(result, value)
                if test is None:
                    value = "%d" % self.normNumber(int(value) & int(result))
                else:
                    self.errExit ("'%s' is not a valid op number for AND" % test)
            else:
                break
        return value

    def parseOrXor(self):
        value  = self.parseAnd()
        op     = None
        while True:
            self.skipSpace()
            op = None
            if re.match('^XOR\W', self.getCurr(-1)):
                self.moveNext(3)
                op = '^'
            elif re.match('^OR\W', self.getCurr(-1)):
                self.moveNext(2)
                op = '|'
            else:
                break
            if op:
                result = self.parseAnd()
                test = self.getNonNumber(result, value)
                if test is None:
                    value = "%d" % self.normNumber(eval (value + op + result))
                else:
                    self.errExit ("'%s' is not a valid op number for XOR/OR" % test)
        return value

    def parseExpr(self):
        return self.parseOrXor()

    def getResult(self):
        value = self.parseExpr()
        self.skipSpace()
        if not self.isLast():
            self.errExit ("Unexpected character found '%s'" % self.getCurr())
        test = self.getNumber(value)
        if test is None:
            self.errExit ("Result '%s' is not a number" % value)
        return int(value)

    def evaluateExpress (self, Expr):
        self.index     = 0
        self.string    = Expr
        if self.getResult():
            Result = True
        else:
            Result = False
        return Result

class CGenCfgOpt:
    def __init__(self):
        self.Debug          = False
        self.Error          = ''

        self._GlobalDataDef = """
GlobalDataDef
    SKUID = 0, "DEFAULT"
EndGlobalData

"""
        self._BuidinOptionTxt = """
List &EN_DIS
    Selection 0x1 , "Enabled"
    Selection 0x0 , "Disabled"
EndList

"""

        self._BsfKeyList    = ['FIND','NAME','HELP','TYPE','PAGE','OPTION','ORDER']
        self._HdrKeyList    = ['HEADER','STRUCT', 'EMBED']
        self._BuidinOption  = {'$EN_DIS' : 'EN_DIS'}

        self._MacroDict   = {}
        self._CfgBlkDict  = {}
        self._CfgPageDict = {}
        self._CfgItemList = []
        self._DscFile     = ''
        self._FvDir       = ''
        self._MapVer      = 0

    def ParseMacros (self, MacroDefStr):
        # ['-DABC=1', '-D', 'CFG_DEBUG=1', '-D', 'CFG_OUTDIR=Build']
        self._MacroDict = {}
        IsExpression = False
        for Macro in MacroDefStr:
            if Macro.startswith('-D'):
                IsExpression = True
                if len(Macro) > 2:
                    Macro = Macro[2:]
                else :
                    continue
            if IsExpression:
                IsExpression = False
                Match = re.match("(\w+)=(.+)", Macro)
                if Match:
                    self._MacroDict[Match.group(1)] = Match.group(2)
                else:
                    Match = re.match("(\w+)", Macro)
                    if Match:
                        self._MacroDict[Match.group(1)] = ''
        if len(self._MacroDict) == 0:
            Error = 1
        else:
            Error = 0
            if self.Debug:
                print "INFO : Macro dictionary:"
                for Each in self._MacroDict:
                    print "       $(%s) = [ %s ]" % (Each , self._MacroDict[Each])
        return Error

    def EvaulateIfdef   (self, Macro):
        Result = Macro in self._MacroDict
        if self.Debug:
            print "INFO : Eval Ifdef [%s] : %s" % (Macro, Result)
        return  Result

    def ExpandMacros (self, Input):
        Line = Input
        Match = re.findall("\$\(\w+\)", Input)
        if Match:
            for Each in Match:
              Variable = Each[2:-1]
              if Variable in self._MacroDict:
                  Line = Line.replace(Each, self._MacroDict[Variable])
              else:
                  if self.Debug:
                      print "WARN : %s is not defined" % Each
                  Line = Line.replace(Each, Each[2:-1])
        return Line

    def EvaluateExpress (self, Expr):
        ExpExpr = self.ExpandMacros(Expr)
        LogExpr = CLogicalExpression()
        Result  = LogExpr.evaluateExpress (ExpExpr)
        if self.Debug:
            print "INFO : Eval Express [%s] : %s" % (Expr, Result)
        return Result

    def FormatListValue(self, ConfigDict):
        Struct = ConfigDict['struct']
        if Struct not in ['UINT8','UINT16','UINT32','UINT64']:
            return

        dataarray = []
        binlist = ConfigDict['value'][1:-1].split(',')
        for each in binlist:
            each = each.strip()
            if each.startswith('0x'):
                value = int(each, 16)
            else:
                value = int(each)
            dataarray.append(value)

        unit = int(Struct[4:]) / 8
        if int(ConfigDict['length']) != unit * len(dataarray):
            raise Exception("Array size is not proper for '%s' !" % ConfigDict['cname'])

        bytearray = []
        for each in dataarray:
            value = each
            for loop in xrange(unit):
                bytearray.append("0x%02X" % (value & 0xFF))
                value = value >> 8
        newvalue  = '{'  + ','.join(bytearray) + '}'
        ConfigDict['value'] = newvalue
        return ""

    def ParseDscFile (self, DscFile, FvDir):
        self._CfgItemList = []
        self._CfgPageDict = {}
        self._CfgBlkDict  = {}
        self._DscFile     = DscFile
        self._FvDir       = FvDir

        IsDefSect       = False
        IsUpdSect       = False
        IsVpdSect       = False
        Found           = False

        IfStack         = []
        ElifStack       = []
        Error           = 0
        ConfigDict      = {}

        DscFd        = open(DscFile, "r")
        DscLines     = DscFd.readlines()
        DscFd.close()

        while len(DscLines):
            DscLine  = DscLines.pop(0).strip()
            Handle   = False
            Match    = re.match("^\[(.+)\]", DscLine)
            if Match is not None:
                if  Match.group(1).lower() == "Defines".lower():
                    IsDefSect = True
                    IsVpdSect = False
                    IsUpdSect = False
                elif Match.group(1).lower() == "PcdsDynamicVpd".lower():
                    ConfigDict = {}
                    ConfigDict['header']  = 'ON'
                    ConfigDict['region']  = 'VPD'
                    ConfigDict['order']   = -1
                    ConfigDict['page']    = ''
                    ConfigDict['name']    = ''
                    ConfigDict['find']    = ''
                    ConfigDict['struct']  = ''
                    ConfigDict['embed']   = ''
                    ConfigDict['subreg']  = []
                    IsDefSect = False
                    IsVpdSect = True
                    IsUpdSect = False
                elif Match.group(1).lower() == "PcdsDynamicVpd.Upd".lower():
                    ConfigDict = {}
                    ConfigDict['header']  = 'ON'
                    ConfigDict['region']  = 'UPD'
                    ConfigDict['order']   = -1
                    ConfigDict['page']    = ''
                    ConfigDict['name']    = ''
                    ConfigDict['find']    = ''
                    ConfigDict['struct']  = ''
                    ConfigDict['embed']   = ''
                    ConfigDict['subreg']  = []
                    IsDefSect = False
                    IsUpdSect = True
                    IsVpdSect = False
                    Found     = True
                else:
                    IsDefSect = False
                    IsUpdSect = False
                    IsVpdSect = False
            else:
                if IsDefSect or IsUpdSect or IsVpdSect:
                    if re.match("^!else($|\s+#.+)", DscLine):
                        if IfStack:
                            IfStack[-1] = not IfStack[-1]
                        else:
                            print("ERROR: No paired '!if' found for '!else' for line '%s'" % DscLine)
                            raise SystemExit
                    elif re.match("^!endif($|\s+#.+)", DscLine):
                        if IfStack:
                            IfStack.pop()
                            Level = ElifStack.pop()
                            if Level > 0:
                                del IfStack[-Level:]
                        else:
                            print("ERROR: No paired '!if' found for '!endif' for line '%s'" % DscLine)
                            raise SystemExit
                    else:
                        Result = False
                        Match = re.match("!(ifdef|ifndef)\s+(.+)", DscLine)
                        if Match:
                            Result = self.EvaulateIfdef (Match.group(2))
                            if Match.group(1) == 'ifndef':
                                Result = not Result
                            IfStack.append(Result)
                            ElifStack.append(0)
                        else:
                            Match  = re.match("!(if|elseif)\s+(.+)", DscLine)
                            if Match:
                                Result = self.EvaluateExpress(Match.group(2))
                                if Match.group(1) == "if":
                                    ElifStack.append(0)
                                    IfStack.append(Result)
                                else:   #elseif
                                    if IfStack:
                                        IfStack[-1] = not IfStack[-1]
                                        IfStack.append(Result)
                                        ElifStack[-1] = ElifStack[-1] + 1
                                    else:
                                        print("ERROR: No paired '!if' found for '!elif' for line '%s'" % DscLine)
                                        raise SystemExit
                            else:
                                if IfStack:
                                    Handle = reduce(lambda x,y: x and y, IfStack)
                                else:
                                    Handle = True
                                if Handle:
                                    Match = re.match("!include\s+(.+)", DscLine)
                                    if Match:
                                        IncludeFilePath = Match.group(1)
                                        IncludeFilePath = self.ExpandMacros(IncludeFilePath)
                                        try:
                                            IncludeDsc  = open(IncludeFilePath, "r")
                                        except:
                                            print("ERROR: Cannot open file '%s'" % IncludeFilePath)
                                            raise SystemExit
                                        NewDscLines = IncludeDsc.readlines()
                                        IncludeDsc.close()
                                        DscLines = NewDscLines + DscLines
                                    else:
                                        if DscLine.startswith('!'):
                                            print("ERROR: Unrecoginized directive for line '%s'" % DscLine)
                                            raise SystemExit
            if not Handle:
                continue

            if IsDefSect:
                #DEFINE UPD_TOOL_GUID = 8C3D856A-9BE6-468E-850A-24F7A8D38E09
                Match = re.match("^\s*(?:DEFINE\s+)*(\w+)\s*=\s*([-.\w]+)", DscLine)
                if Match:
                    self._MacroDict[Match.group(1)] = Match.group(2)
                    if self.Debug:
                        print "INFO : DEFINE %s = [ %s ]" % (Match.group(1), Match.group(2))
            else:
                Match = re.match("^\s*#\s+!(BSF|HDR)\s+(.+)", DscLine)
                if Match:
                    Remaining = Match.group(2)
                    if Match.group(1) == 'BSF':
                        Match = re.match("(?:^|.+\s+)PAGES:{(.+?)}", Remaining)
                        if Match:
                            # !BSF PAGES:{HSW:"Haswell System Agent", LPT:"Lynx Point PCH"}
                            PageList = Match.group(1).split(',')
                            for Page in PageList:
                                Page  = Page.strip()
                                Match = re.match("(\w+):\"(.+)\"", Page)
                                self._CfgPageDict[Match.group(1)] = Match.group(2)

                        Match = re.match("(?:^|.+\s+)BLOCK:{NAME:\"(.+)\"\s*,\s*VER:\"(.+)\"\s*}", Remaining)
                        if Match:
                            self._CfgBlkDict['name'] = Match.group(1)
                            self._CfgBlkDict['ver']  = Match.group(2)

                        for Key in self._BsfKeyList:
                            Match = re.match("(?:^|.+\s+)%s:{(.+?)}" % Key, Remaining)
                            if Match:
                                if Key in ['NAME', 'HELP', 'OPTION'] and Match.group(1).startswith('+'):
                                    ConfigDict[Key.lower()] += Match.group(1)[1:]
                                else:
                                    ConfigDict[Key.lower()]  = Match.group(1)
                    else:
                        for Key in self._HdrKeyList:
                            Match = re.match("(?:^|.+\s+)%s:{(.+?)}" % Key, Remaining)
                            if Match:
                                ConfigDict[Key.lower()]  = Match.group(1)

                # Check VPD/UPD
                if IsUpdSect:
                    Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)\s*\|\s*(\d+|0x[0-9a-fA-F]+)\s*\|\s*(.+)",DscLine)
                else:
                    Match = re.match("^([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)\s*\|\s*(0x[0-9A-F]+)(?:\s*\|\s*(.+))?",  DscLine)
                if Match:
                    ConfigDict['space']  = Match.group(1)
                    ConfigDict['cname']  = Match.group(2)
                    ConfigDict['offset'] = int (Match.group(3), 16)
                    if ConfigDict['order'] == -1:
                        ConfigDict['order'] = ConfigDict['offset'] << 8
                    else:
                        (Major, Minor) = ConfigDict['order'].split('.')
                        ConfigDict['order'] = (int (Major, 16) << 8 ) +  int (Minor, 16)
                    if IsUpdSect:
                        Value = Match.group(5).strip()
                        if Match.group(4).startswith("0x"):
                            Length  = int (Match.group(4), 16)
                        else :
                            Length  = int (Match.group(4))
                    else:
                        Value = Match.group(4)
                        if Value is None:
                            Value = ''
                        Value = Value.strip()
                        if '|' in Value:
                            Match = re.match("^.+\s*\|\s*(.+)", Value)
                            if Match:
                                Value = Match.group(1)
                        Length = -1

                    ConfigDict['length'] = Length
                    Match = re.match("\$\((\w+)\)", Value)
                    if Match:
                        if Match.group(1) in self._MacroDict:
                            Value = self._MacroDict[Match.group(1)]

                    ConfigDict['value']  = Value
                    if (len(Value) > 0)  and (Value[0] == '{'):
                        Value = self.FormatListValue(ConfigDict)

                    if ConfigDict['name']  == '':
                        # Clear BSF specific items
                        ConfigDict['bsfname']   = ''
                        ConfigDict['help']   = ''
                        ConfigDict['type']   = ''
                        ConfigDict['option'] = ''

                    self._CfgItemList.append(ConfigDict.copy())
                    ConfigDict['name']   = ''
                    ConfigDict['find']   = ''
                    ConfigDict['struct'] = ''
                    ConfigDict['embed']  = ''
                    ConfigDict['order']  = -1
                    ConfigDict['subreg'] = []
                else:
                    # It could be a virtual item as below
                    # !BSF FIELD:{1:SerialDebugPortAddress0}
                    Match = re.match("^\s*#\s+!BSF\s+FIELD:{(.+):(\d+)}", DscLine)
                    if Match:
                        SubCfgDict = ConfigDict
                        SubCfgDict['cname']  = Match.group(1)
                        SubCfgDict['length'] = int (Match.group(2))
                        if SubCfgDict['length'] > 0:
                            LastItem =  self._CfgItemList[-1]
                            if len(LastItem['subreg']) == 0:
                                SubOffset  = 0
                            else:
                                SubOffset += LastItem['subreg'][-1]['length']
                            SubCfgDict['offset'] = SubOffset
                            LastItem['subreg'].append (SubCfgDict.copy())
                        ConfigDict['name']   = ''
        return Error

    def UpdateSubRegionDefaultValue (self):
        Error = 0
        for Item in self._CfgItemList:
            if len(Item['subreg']) == 0:
                continue
            bytearray = []
            if Item['value'][0] == '{':
                binlist = Item['value'][1:-1].split(',')
                for each in binlist:
                    each = each.strip()
                    if each.startswith('0x'):
                        value = int(each, 16)
                    else:
                        value = int(each)
                    bytearray.append(value)
            else:
                if Item['value'].startswith('0x'):
                    value = int(Item['value'], 16)
                else:
                    value = int(Item['value'])
                idx = 0;
                while  idx < Item['length']:
                    bytearray.append(value & 0xFF)
                    value = value >> 8
                    idx = idx + 1
            for SubItem in Item['subreg']:
                if SubItem['length'] in (1,2,4,8):
                    valuelist = [b for b in bytearray[SubItem['offset']:SubItem['offset']+SubItem['length']]]
                    valuelist.reverse()
                    valuestr = "".join('%02X' % b for b in valuelist)
                    SubItem['value'] = '0x%s' % valuestr
                else:
                    valuestr = ",".join('0x%02X' % b for b in bytearray[SubItem['offset']:SubItem['offset']+SubItem['length']])
                    SubItem['value'] = '{%s}' % valuestr
        return Error

    def UpdateVpdSizeField (self):
        FvDir = self._FvDir;

        if 'VPD_TOOL_GUID' not in self._MacroDict:
            self.Error = "VPD_TOOL_GUID definition is missing in DSC file"
            return 1

        VpdMapFile = os.path.join(FvDir, self._MacroDict['VPD_TOOL_GUID'] + '.map')
        if not os.path.exists(VpdMapFile):
            self.Error = "VPD MAP file '%s' does not exist" % VpdMapFile
            return 2

        MapFd       = open(VpdMapFile, "r")
        MapLines    = MapFd.readlines()
        MapFd.close()

        VpdDict  = {}
        PcdDict  = {}
        for MapLine in MapLines:
            #gPlatformFspPkgTokenSpaceGuid.PcdVpdRegionSign | DEFAULT | 0x0000 | 8 | 0x534450565F425346
            #gPlatformFspPkgTokenSpaceGuid.PcdVpdRegionSign | 0x0000 | 8 | 0x534450565F425346
            #gPlatformFspPkgTokenSpaceGuid.PcdTest          | 0x0008 | 5 | {0x01,0x02,0x03,0x04,0x05}
            Match = re.match("([_a-zA-Z0-9]+).([_a-zA-Z0-9]+)(\s\|\sDEFAULT)?\s\|\s(0x[0-9A-F]{4})\s\|\s(\d+|0x[0-9a-fA-F]+)\s\|\s(\{?[x0-9a-fA-F,\s]+\}?)", MapLine)
            if Match:
                Space  = Match.group(1)
                Name   = Match.group(2)
                if (self._MapVer == 0) and (Match.group(3) != None):
                    self._MapVer = 1
                Offset = int (Match.group(4), 16)
                if Match.group(5).startswith("0x"):
                    Length = int (Match.group(5), 16)
                else :
                    Length = int (Match.group(5))
                PcdDict["len"]   = Length
                PcdDict["value"]   = Match.group(6)
                VpdDict[Space+'.'+Name] = dict(PcdDict)

        for Item in self._CfgItemList:
            if Item['value'] == '':
                Item['value']  = VpdDict[Item['space']+'.'+Item['cname']]['value']
            if Item['length'] == -1:
                Item['length'] = VpdDict[Item['space']+'.'+Item['cname']]['len']
            if Item['struct'] != '':
                Type = Item['struct'].strip()
                if Type.endswith('*') and (Item['length'] != 4):
                    self.Error = "Struct pointer '%s' has invalid size" % Type
                    return 3

        return 0

    def CreateUpdTxtFile (self, UpdTxtFile):
        FvDir = self._FvDir
        if 'UPD_TOOL_GUID' not in self._MacroDict:
            self.Error = "UPD_TOOL_GUID definition is missing in DSC file"
            return 1

        if UpdTxtFile == '':
            UpdTxtFile = os.path.join(FvDir, self._MacroDict['UPD_TOOL_GUID'] + '.txt')

        ReCreate = False
        if not os.path.exists(UpdTxtFile):
            ReCreate = True
        else:
            DscTime = os.path.getmtime(self._DscFile)
            TxtTime = os.path.getmtime(UpdTxtFile)
            if DscTime > TxtTime:
                ReCreate = True

        if not  ReCreate:
            # DSC has not been modified yet
            # So don't have to re-generate other files
            self.Error = 'No DSC file change, skip to create UPD TXT file'
            return 256

        TxtFd = open(UpdTxtFile, "w")
        TxtFd.write("%s\n"   % (__copyright_txt__ % date.today().year))

        NextOffset = 0
        SpaceIdx   = 0
        if self._MapVer == 1:
            Default = 'DEFAULT|'
        else:
            Default = ''
        for Item in self._CfgItemList:
            if Item['region'] != 'UPD':
                continue
            Offset = Item['offset']
            if NextOffset < Offset:
                # insert one line
                TxtFd.write("%s.UnusedUpdSpace%d|%s0x%04X|0x%04X|{0}\n" % (Item['space'], SpaceIdx, Default, NextOffset, Offset - NextOffset))
                SpaceIdx = SpaceIdx + 1
            NextOffset = Offset + Item['length']
            TxtFd.write("%s.%s|%s0x%04X|%s|%s\n" % (Item['space'],Item['cname'],Default,Item['offset'],Item['length'],Item['value']))
        TxtFd.close()
        return 0

    def CreateField (self, Item, Name, Length, Offset, Struct, BsfName, Help):
        PosName    = 28
        PosComment = 30
        NameLine=''
        HelpLine=''

        IsArray = False
        if Length in [1,2,4,8]:
            Type = "UINT%d" % (Length * 8)
        else:
            IsArray = True
            Type = "UINT8"

        if Item and Item['value'].startswith('{'):
            Type = "UINT8"
            IsArray = True

        if Struct != '':
            Type = Struct
            if Struct in ['UINT8','UINT16','UINT32','UINT64']:
                IsArray = True
                Unit = int(Type[4:]) / 8
                Length = Length / Unit
            else:
                IsArray = False

        if IsArray:
            Name = Name + '[%d]' % Length

        if len(Type) < PosName:
            Space1 = PosName - len(Type)
        else:
            Space1 = 1

        if BsfName != '':
            NameLine="    %s\n" % BsfName

        if Help != '':
            HelpLine="    %s\n" % Help

        if Offset is None:
            OffsetStr = '????'
        else:
            OffsetStr = '0x%04X' % Offset

        return "/** Offset %s\n%s%s**/\n  %s%s%s;\n" % (OffsetStr, NameLine, HelpLine, Type, ' ' * Space1, Name,)

    def PostProcessBody (self, TextBody):
        NewTextBody = []
        OldTextBody = []
        IncludeLine = False
        StructName  = ''
        VariableName = ''
        for Line in TextBody:
           Match = re.match("^/\*\sEMBED_STRUCT:(\w+):(\w+):(START|END)\s\*/\s([\s\S]*)", Line)
           if Match:
               Line = Match.group(4)

           if Match and Match.group(3) == 'START':
               NewTextBody.append ('typedef struct {\n')
               StructName   = Match.group(1)
               VariableName = Match.group(2)
               MatchOffset = re.search('/\*\*\sOffset\s0x([a-fA-F0-9]+)', Line)
               if MatchOffset:
                   Offset = int(MatchOffset.group(1), 16)
               else:
                   Offset = None
               Line
               IncludeLine = True
               OldTextBody.append (self.CreateField (None, VariableName, 0, Offset, StructName, '', ''))
           if IncludeLine:
               NewTextBody.append (Line)
           else:
               OldTextBody.append (Line)

           if Match and Match.group(3) == 'END':  
               if (StructName != Match.group(1)) or (VariableName != Match.group(2)):
                   print "Unmatched struct name '%s' and '%s' !"  % (StructName, Match.group(1))
               else:
                   NewTextBody.append ('} %s;\n\n' %  StructName)
               IncludeLine = False
        NewTextBody.extend(OldTextBody)
        return NewTextBody

    def CreateHeaderFile (self, InputHeaderFile, IsInternal):
        FvDir = self._FvDir

        if IsInternal:
            HeaderFile = os.path.join(FvDir, 'FspUpdVpdInternal.h')
        else:
            HeaderFile = os.path.join(FvDir, 'FspUpdVpd.h')

        # Check if header needs to be recreated
        ReCreate = False
        if IsInternal:
            if not os.path.exists(HeaderFile):
                ReCreate = True
            else:
                DscTime  = os.path.getmtime(self._DscFile)
                HeadTime = os.path.getmtime(HeaderFile)
                if not os.path.exists(InputHeaderFile):
                    InpTime =  HeadTime
                else:
                    InpTime  = os.path.getmtime(InputHeaderFile)
                if DscTime > HeadTime or InpTime > HeadTime:
                    ReCreate = True

            if not ReCreate:
                self.Error = "No DSC or input header file is changed, skip the header file generating"
                return 256

        TxtBody = []
        for Item in self._CfgItemList:
           if str(Item['cname']) == 'Signature' and Item['length'] == 8:
               Value = int(Item['value'], 16)
               Chars = []
               while Value != 0x0:
                   Chars.append(chr(Value & 0xFF))
                   Value = Value >> 8
               SignatureStr = ''.join(Chars)
               if int(Item['offset']) == 0:
                   TxtBody.append("#define FSP_UPD_SIGNATURE                %s        /* '%s' */\n" % (Item['value'], SignatureStr))
               elif 'MEM' in SignatureStr:
                   TxtBody.append("#define FSP_MEMORY_INIT_UPD_SIGNATURE    %s        /* '%s' */\n" % (Item['value'], SignatureStr))
               else:
                   TxtBody.append("#define FSP_SILICON_INIT_UPD_SIGNATURE   %s        /* '%s' */\n" % (Item['value'], SignatureStr))
        TxtBody.append("\n")

        for Region in ['UPD', 'VPD']:

            # Write  PcdVpdRegionSign and PcdImageRevision
            if Region[0] == 'V':
                if 'VPD_TOOL_GUID' not in self._MacroDict:
                    self.Error = "VPD_TOOL_GUID definition is missing in DSC file"
                    return 1

                BinFile = os.path.join(FvDir, self._MacroDict['VPD_TOOL_GUID'] + ".bin")
                if not os.path.exists(BinFile):
                    self.Error = "VPD binary file '%s' does not exist" % BinFile
                    return 2

                BinFd = open(BinFile, "rb")
                IdStr    = BinFd.read(0x08)
                ImageId  = struct.unpack('<Q', IdStr)
                ImageRev = struct.unpack('<I', BinFd.read(0x04))
                BinFd.close()

                TxtBody.append("#define FSP_IMAGE_ID    0x%016X        /* '%s' */\n" % (ImageId[0], IdStr))
                TxtBody.append("#define FSP_IMAGE_REV   0x%08X \n\n" % ImageRev[0])

            TxtBody.append("typedef struct _" + Region[0]  + "PD_DATA_REGION {\n")
            NextOffset  = 0
            SpaceIdx    = 0
            Offset      = 0

            LastVisible = True
            ResvOffset  = 0
            ResvIdx     = 0
            LineBuffer  = []
            for Item in self._CfgItemList:
                if Item['region'] != Region:
                    continue

                NextVisible = LastVisible
                if not IsInternal:
                    if LastVisible and (Item['header'] == 'OFF'):
                        NextVisible = False
                        ResvOffset  = Item['offset']
                    elif (not LastVisible) and Item['header'] == 'ON':
                        NextVisible = True
                        Name = "Reserved" + Region[0] + "pdSpace%d" % ResvIdx
                        ResvIdx = ResvIdx + 1
                        TxtBody.append(self.CreateField (Item, Name, Item["offset"] - ResvOffset, ResvOffset, '', '', ''))

                if  Offset < Item["offset"]:
                    if IsInternal or LastVisible:
                        Name = "Unused" + Region[0] + "pdSpace%d" % SpaceIdx
                        LineBuffer.append(self.CreateField (Item, Name, Item["offset"] - Offset, Offset, '', '', ''))
                    SpaceIdx = SpaceIdx + 1
                    Offset   = Item["offset"]

                if Offset != Item["offset"]:
                    self.Error = "Unsorted offset 0x%04X\n" % Item["offset"]
                    return 3                    

                LastVisible = NextVisible

                Offset = Offset + Item["length"]
                if IsInternal or LastVisible:
                    for Each in LineBuffer:
                        TxtBody.append (Each)
                    LineBuffer = []
                    Embed = Item["embed"].upper()
                    if Embed.endswith(':START') or Embed.endswith(':END'):
                        Marker = '/* EMBED_STRUCT:%s */ ' % Item["embed"]
                    else:
                        if Embed == '':
                            Marker = '';
                        else:
                            self.Error = "Invalid embedded structure format '%s'!\n" % Item["embed"]
                            return 4
                    Line = Marker + self.CreateField (Item, Item["cname"], Item["length"], Item["offset"], Item['struct'], Item['name'], Item['help'])
                    TxtBody.append(Line)
                    
            TxtBody.append("} " + Region[0] + "PD_DATA_REGION;\n\n")
        
        # Handle the embedded data structure
        TxtBody = self.PostProcessBody (TxtBody)

        HeaderFd = open(HeaderFile, "w")
        FileBase = os.path.basename(HeaderFile)
        FileName = FileBase.replace(".", "_").upper()
        HeaderFd.write("%s\n"   % (__copyright_h__ % date.today().year))
        HeaderFd.write("#ifndef __%s__\n"   % FileName)
        HeaderFd.write("#define __%s__\n\n" % FileName)
        HeaderFd.write("#pragma pack(1)\n\n")

        if InputHeaderFile != '':
            if not os.path.exists(InputHeaderFile):
                 self.Error = "Input header file '%s' does not exist" % InputHeaderFile
                 return 6

            InFd         = open(InputHeaderFile, "r")
            IncLines     = InFd.readlines()
            InFd.close()

            Export = False
            for Line in IncLines:
                Match = re.search ("!EXPORT\s+EXTERNAL_BOOTLOADER_STRUCT_(BEGIN|END)\s+", Line)
                if Match:
                    if Match.group(1) == "BEGIN":
                        Export = True
                        continue
                    else:
                        Export = False
                        continue
                if Export:
                    HeaderFd.write(Line)
            HeaderFd.write("\n\n")
            
        for Line in TxtBody:
            HeaderFd.write (Line)
        HeaderFd.write("#pragma pack()\n\n")
        HeaderFd.write("#endif\n")
        HeaderFd.close()

        return 0

    def WriteBsfStruct  (self, BsfFd, Item):
        if Item['type'] == "None":
            Space = "gPlatformFspPkgTokenSpaceGuid"
        else:
            Space = Item['space']
        Line = "    $%s_%s" % (Space, Item['cname'])
        Match = re.match("\s*\{([x0-9a-fA-F,\s]+)\}\s*", Item['value'])
        if Match:
            DefaultValue = Match.group(1).strip()
        else:
            DefaultValue = Item['value'].strip()
        BsfFd.write("    %s%s%4d bytes    $_DEFAULT_ = %s\n" % (Line, ' ' * (64 - len(Line)), Item['length'], DefaultValue))
        TmpList = []
        if  Item['type'] == "Combo":
            if not Item['option'] in self._BuidinOption:
                OptList = Item['option'].split(',')
                for Option in OptList:
                    Option = Option.strip()
                    (OpVal, OpStr) = Option.split(':')
                    TmpList.append((OpVal, OpStr))
        return  TmpList

    def WriteBsfOption  (self, BsfFd, Item):
        PcdName   = Item['space'] + '_' + Item['cname']
        WriteHelp = 0
        if Item['type'] == "Combo":
            if Item['option'] in self._BuidinOption:
                Options = self._BuidinOption[Item['option']]
            else:
                Options = PcdName
            BsfFd.write('    %s $%s, "%s", &%s,\n' % (Item['type'], PcdName, Item['name'], Options));
            WriteHelp = 1
        elif Item['type'].startswith("EditNum"):
            Match = re.match("EditNum\s*,\s*(HEX|DEC)\s*,\s*\((\d+|0x[0-9A-Fa-f]+)\s*,\s*(\d+|0x[0-9A-Fa-f]+)\)", Item['type'])
            if Match:
                BsfFd.write('    EditNum $%s, "%s", %s,\n' % (PcdName, Item['name'], Match.group(1)));
                WriteHelp = 2
        elif Item['type'].startswith("EditText"):
            BsfFd.write('    %s $%s, "%s",\n' % (Item['type'], PcdName, Item['name']));
            WriteHelp = 1
        elif Item['type'] == "Table":
            Columns = Item['option'].split(',')
            if len(Columns) != 0:
                BsfFd.write('    %s $%s "%s",' % (Item['type'], PcdName, Item['name']));
                for Col in Columns:
                    Fmt = Col.split(':')
                    if len(Fmt) != 3:
                        raise Exception("Column format '%s' is invalid !" % Fmt)
                    try:
                        Dtype = int(Fmt[1].strip())
                    except:
                        raise Exception("Column size '%s' is invalid !" % Fmt[1])
                    BsfFd.write('\n        Column "%s", %d bytes, %s' % (Fmt[0].strip(), Dtype, Fmt[2].strip()))
                BsfFd.write(',\n')
                WriteHelp = 1
            
        if WriteHelp  > 0:
            HelpLines = Item['help'].split('\\n\\r')
            FirstLine = True
            for HelpLine in HelpLines:
                if FirstLine:
                    FirstLine = False
                    BsfFd.write('        Help "%s"\n' % (HelpLine));
                else:
                    BsfFd.write('             "%s"\n' % (HelpLine));
            if WriteHelp == 2:
                    BsfFd.write('             "Valid range: %s ~ %s"\n' % (Match.group(2), Match.group(3)));

    def GenerateBsfFile (self, BsfFile):

        if BsfFile == '':
            self.Error = "BSF output file '%s' is invalid" % BsfFile
            return 1

        Error = 0
        OptionDict = {}
        BsfFd      = open(BsfFile, "w")
        BsfFd.write("%s\n" % (__copyright_bsf__ % date.today().year))
        BsfFd.write("%s\n" % self._GlobalDataDef);
        BsfFd.write("StructDef\n")
        NextOffset = -1
        for Item in self._CfgItemList:
            if Item['find'] != '':
                BsfFd.write('\n    Find "%s"\n' % Item['find'])
                NextOffset = Item['offset'] + Item['length']
            if Item['name'] != '':
                if NextOffset != Item['offset']:
                    BsfFd.write("        Skip %d bytes\n" % (Item['offset'] - NextOffset))
                if len(Item['subreg']) > 0:
                    NextOffset =  Item['offset']
                    for SubItem in Item['subreg']:
                        NextOffset += SubItem['length']
                        if SubItem['name'] == '':
                            BsfFd.write("        Skip %d bytes\n" % (SubItem['length']))
                        else:
                            Options = self.WriteBsfStruct(BsfFd, SubItem)
                            if len(Options) > 0:
                                OptionDict[SubItem['space']+'_'+SubItem['cname']] = Options
                    if (Item['offset'] + Item['length']) <  NextOffset:
                        self.Error = "BSF sub region '%s' length does not match" % (Item['space']+'.'+Item['cname'])
                        return 2
                else:
                    NextOffset = Item['offset'] + Item['length']
                    Options = self.WriteBsfStruct(BsfFd, Item)
                    if len(Options) > 0:
                        OptionDict[Item['space']+'_'+Item['cname']] = Options
        BsfFd.write("\nEndStruct\n\n")

        BsfFd.write("%s" % self._BuidinOptionTxt);

        for Each in OptionDict:
            BsfFd.write("List &%s\n" % Each);
            for Item in OptionDict[Each]:
                BsfFd.write('    Selection %s , "%s"\n' % (Item[0], Item[1]));
            BsfFd.write("EndList\n\n");

        BsfFd.write("BeginInfoBlock\n");
        BsfFd.write('    PPVer       "%s"\n' % (self._CfgBlkDict['ver']));
        BsfFd.write('    Description "%s"\n' % (self._CfgBlkDict['name']));
        BsfFd.write("EndInfoBlock\n\n");

        for Each in self._CfgPageDict:
            BsfFd.write('Page "%s"\n' % self._CfgPageDict[Each]);
            BsfItems = []
            for Item in self._CfgItemList:
                if Item['name'] != '':
                    if Item['page'] != Each:
                        continue
                    if len(Item['subreg']) > 0:
                        for SubItem in Item['subreg']:
                            if SubItem['name'] != '':
                                BsfItems.append(SubItem)
                    else:
                        BsfItems.append(Item)

            BsfItems.sort(key=lambda x: x['order'])

            for Item in BsfItems:
                self.WriteBsfOption (BsfFd, Item)
            BsfFd.write("EndPage\n\n");

        BsfFd.close()
        return  Error


def Usage():
    print "GenCfgOpt Version 0.50"
    print "Usage:"
    print "    GenCfgOpt  UPDTXT  PlatformDscFile BuildFvDir  [TxtOutFile]     [-D Macros]"
    print "    GenCfgOpt  HEADER  PlatformDscFile BuildFvDir  [InputHFile]     [-D Macros]"
    print "    GenCfgOpt  GENBSF  PlatformDscFile BuildFvDir  BsfOutFile       [-D Macros]"

def Main():
    #
    # Parse the options and args
    #
    GenCfgOpt = CGenCfgOpt()
    argc = len(sys.argv)
    if argc < 4:
        Usage()
        return 1
    else:
        DscFile = sys.argv[2]
        if not os.path.exists(DscFile):
            print "ERROR: Cannot open DSC file '%s' !" % DscFile
            return 2

        UpdateMemSiUpdInitOffsetValue(DscFile)

        OutFile = ''
        if argc > 4:
            if sys.argv[4][0] == '-':
                Start = 4
            else:
                OutFile = sys.argv[4]
                Start = 5
            if GenCfgOpt.ParseMacros(sys.argv[Start:]) != 0:
                print "ERROR: Macro parsing failed !"
                return 3

        FvDir = sys.argv[3]
        if not os.path.isdir(FvDir):
            print "ERROR: FV folder '%s' is invalid !" % FvDir
            return 4

        if GenCfgOpt.ParseDscFile(DscFile, FvDir) != 0:
            print "ERROR: %s !" % GenCfgOpt.Error
            return 5

        if GenCfgOpt.UpdateVpdSizeField() != 0:
            print "ERROR: %s !" % GenCfgOpt.Error
            return 6

        if GenCfgOpt.UpdateSubRegionDefaultValue() != 0:
            print "ERROR: %s !" % GenCfgOpt.Error
            return 7

        if sys.argv[1] == "UPDTXT":
            Ret = GenCfgOpt.CreateUpdTxtFile(OutFile)
            if Ret != 0:
                # No change is detected
                if Ret == 256:
                    print "INFO: %s !" % (GenCfgOpt.Error)
                else :
                    print "ERROR: %s !" % (GenCfgOpt.Error)
                return Ret
        elif sys.argv[1] == "HEADER":
            Ret = GenCfgOpt.CreateHeaderFile(OutFile, True)
            if Ret != 0:
                # No change is detected
                if Ret == 256:
                    print "INFO: %s !" % (GenCfgOpt.Error)
                else :
                    print "ERROR: %s !" % (GenCfgOpt.Error)
                return Ret
            if GenCfgOpt.CreateHeaderFile(OutFile, False) != 0:
                print "ERROR: %s !" % GenCfgOpt.Error
                return 8
        elif sys.argv[1] == "GENBSF":
            if GenCfgOpt.GenerateBsfFile(OutFile) != 0:
                print "ERROR: %s !" % GenCfgOpt.Error
                return 9
        else:
            if argc < 5:
                Usage()
                return 1
            print "ERROR: Unknown command '%s' !" % sys.argv[1]
            Usage()
            return 1
        return 0
    return 0


if __name__ == '__main__':
    sys.exit(Main())
